using System;
using System.Text.RegularExpressions;
using System.Xml.XPath;
using System.Xml.Xsl;

namespace PaulRz.Util
{
    public abstract class NoParameterFunction : IXsltContextFunction
    {
        public abstract object Invoke(XsltContext xsltContext, object[] args, XPathNavigator docContext);

        public int Minargs
        {
            get { return 0; }
        }

        public int Maxargs
        {
            get { return 0; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.Any; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {}; }
        }
    }

    /// <summary>
    /// Adds own implementation of missing ternal operator support. Function has three parameters.
    /// The first one is condition. If contidion is evaluated as true the second parameter is returned, if false the third one is returned.
    /// 
    /// Syntax:
    /// if(condition, ifTrue, ifFalse)
    /// </summary>
    public class IfFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 3; }
        }

        public int Maxargs
        {
            get { return 3; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.Any; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.Boolean, XPathResultType.Any, XPathResultType.Any}; }
        }

        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            return (Convert.ToBoolean(args[0]) ? args[1] : args[2]);
        }
    }


    /// <summary>
    /// Returns root node specified XSLT documet. Usage is the same as XSLT's document() function.
    /// document() function supports only 1 parameter - document name.
    /// Class requires <see cref="XSLTDocumentContext"/> class as passed context.
    /// </summary>
    public class DocumentFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 1; }
        }

        public int Maxargs
        {
            get { return 1; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.NodeSet; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String}; }
        }

        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            ((XSLTDocumentContext) xsltContext).Current = docContext.Select("/").Current;
            return XPathCache.Instance.GetXPathDocument((string) args[0]).CreateNavigator().Select(".");
        }
    }

    /// <summary>
    /// Returns root node of main document in which was document() function called
    /// Class requires <see cref="XSLTDocumentContext"/> class as passed context.
    /// </summary>
    public class CurrentFunction : NoParameterFunction
    {
        public override object Invoke(XsltContext xsltContext,
                                      object[] args, XPathNavigator docContext)
        {
            XSLTDocumentContext context = (XSLTDocumentContext) xsltContext;
            return (context.Current == null ? docContext : context.Current).Select(".");
        }
    }


    /// <summary>
    /// Returns current date
    /// </summary>
    public class NowFunction : NoParameterFunction
    {
        public override object Invoke(XsltContext xsltContext,
                                      object[] args, XPathNavigator docContext)
        {
            return DateTime.Today.ToString("d", FormatProvider.CRM);
        }
    }


    /// <summary>
    /// Returns the first day of month in specified date
    /// </summary>
    public class FirstDayOfMonthFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 1; }
        }

        public int Maxargs
        {
            get { return 1; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.String; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String}; }
        }


        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            string date = args[0].ToString();
            DateTime d = DateTime.Parse(date, FormatProvider.CRM);
            d = d.AddDays(1 - d.Day);
            return d.ToString("d", FormatProvider.CRM);
        }
    }

    /// <summary>
    /// Returns the last day of month in specified date
    /// </summary>
    public class LastDayOfMonthFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 1; }
        }

        public int Maxargs
        {
            get { return 1; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.String; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String}; }
        }


        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            string date = args[0].ToString();
            DateTime d = DateTime.Parse(date, FormatProvider.CRM);
            d = d.AddMonths(1).AddDays(-d.Day);
            return d.ToString("d", FormatProvider.CRM);
        }
    }

    /// <summary>
    /// Returns date increased by specified amount of months. Value can be negative.
    /// Parameters:
    /// - date you want modify
    /// - amount of days by which should be date increated (descreased for negatives) 
    /// </summary>
    public class AddMonthsFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 2; }
        }

        public int Maxargs
        {
            get { return 2; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.String; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String, XPathResultType.Number}; }
        }


        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            string date = args[0].ToString();
            DateTime d = DateTime.Parse(date, FormatProvider.CRM);
            d = d.AddMonths(Convert.ToInt32(args[1]));
            return d.ToString("d", FormatProvider.CRM);
        }
    }

    /// <summary>
    /// Checks if values specified as first parameter matches given regular expression
    /// </summary>
    public class MatchesFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 2; }
        }

        public int Maxargs
        {
            get { return 2; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.Boolean; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String, XPathResultType.String}; }
        }


        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            string input = FUtil.ToString(args[0]);
            return Regex.IsMatch(input, FUtil.ToString(args[1]));
        }
    }


    /// <summary>
    /// 1. param - input
    /// 2. param - regexp
    /// 3. param - replacement string
    /// </summary>
    public class ReplaceFunction : IXsltContextFunction
    {
        public int Minargs
        {
            get { return 3; }
        }

        public int Maxargs
        {
            get { return 3; }
        }

        public XPathResultType ReturnType
        {
            get { return XPathResultType.String; }
        }

        public XPathResultType[] ArgTypes
        {
            get { return new XPathResultType[] {XPathResultType.String, XPathResultType.String, XPathResultType.String}; }
        }


        public object Invoke(XsltContext xsltContext,
                             object[] args, XPathNavigator docContext)
        {
            return Regex.Replace(FUtil.ToString(args[0]), FUtil.ToString(args[1]), FUtil.ToString(args[2]));
        }
    }
}